package hyl.core.db;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import hyl.core.MyDate;
import hyl.core.MyFun;
import hyl.core.run.MyTimer;

/**
 * 连接方案3 如果断开反复连接,直到连接成功
 * 
 * @author 阿友 3798955@qq.com
 *
 */
public class MyDataSource3 extends MyDataSource {
	private static final Logger Log = LoggerFactory.getLogger(MyDataSource3.class);
	/**
	 * 待用连接池 存放待使用的连接 ArrayBlockingQueue 只有一把锁
	 * 
	 * LinkedBlockingQueue 读取和写入各有一把锁
	 * 
	 * 保存了所有链接 初始为空，第一次使用开始初始化5个
	 */
	private List<MyConnection> connpool = null; // 连接池中存放所有连接指针
	private Queue<MyConnection> queue = new ConcurrentLinkedQueue<>(); // 存放可用的连接
	/*
	 * 在用连接池, 存放 正在使用的连接池,
	 * 
	 * 如果假设MyConnection 是可自动回收的连接对象,那么这个对象可以省略
	 */
	// private Queue<MyConnection> connections2 = null; // , 初始时为 null

	protected MyDataSource3() {
	}

	/** 专门用来定时连接,检查连接，防止连接失效 */
	protected MyTimer _mtimer = null;

	protected MyDataSource3(String name, String jdbcDriver) {
		super(name, jdbcDriver);
	}

	/**
	 * 创建一个数据库连接池，连接池中的可用连接的数量采用类成员 i初始大小 中设置的值
	 */
	public static synchronized MyDataSource newInstance(String name, String jdbcDriver, int 最大连接数) {
		// 实例化 JDBC Driver 中指定的驱动类实例
		Driver driver;
		try {
			driver = (Driver) (Class.forName(jdbcDriver).newInstance());
			DriverManager.registerDriver(driver); // 注册 JDBC 驱动程序
		} catch (Exception e) {
			Log.error("无法加载jdbc驱动：" + jdbcDriver);
			return null;
		}
		MyDataSource3 ds2 = new MyDataSource3(name, jdbcDriver);
		ds2.setI最大连接数量(最大连接数);
		ds2.connpool = new ArrayList<MyConnection>(ds2.getI最大连接数量());
		return ds2;
	}

//////监控任务///////////////
	/*
	 * 每隔1min 检查有没有异常的连接,如果有关闭连接,更换连接,恢复连接状态
	 */
	public synchronized void fv检查所有连接() {
		// 当可用连接数不足时查看连接池，看看有没有可以修复的连接，如果有就尝试修复
		if (i可用连接数.intValue() < i最少连接数) {
			for (int i检查 = 0; i检查 < connpool.size(); i检查++) {
				MyConnection co = connpool.get(i检查);
				showConnInfo(co, i检查);
				// 主要修复异常关闭的情况
				if (co.isExit()) {
					fv绑定连接(co);
					if (co._i状态 != MyConnection.I空闲) {
						fv插入有效队列(co);
					}
				} else if (co.isTimeout()) {// 如果发现超时,先关闭
					co._i状态 = MyConnection.I超时;
					co.exit();
				}
				// 其他情况无需修复
			}
		}
	}

	////////////////// 接口函数 /////////////////////
	// 从使用状态转到可用队列
	@Override
	public void fv立即归还连接(MyConnection co) {// ok
		if (co.isExit()) {// 归还前确认如果连接异常 修复一下
			fv绑定连接(co);
		}
		// showInfo("fv手动归还连接() 连接="+co.getId());
		fv插入有效队列(co);
	}

	protected MyConnection getConnection() {
		MyConnection co = null;
		// 第一次用创建队列 ，并创建5个连接待用
		int i = 0;
		// 重试5次 以后如果还是获取不到会返回null
		// **************** 进入while 后，定时任务不起作用 ***********************
		while (true) {
//如果 最大连接数没有超过界限,队列中的空闲连接数少于最少连接数,就主动创建一批连接
			if ((I最大连接数量 > i在册连接数.intValue()) && (i可用连接数.intValue() <= i最少连接数)) {
				fv批量新增连接();
			}
			// 取出其中一个用
			co = fv从连接池取出下一个连接();
			/***************************
			 * 发生异常以后，只有第一个异常线程跟踪处理连接错误，其他全部阻塞
			 ***************************/
			i++;
			if (co == null) {// 取不出来
				MyDate.sleep(this.i重试连接间隔);
			} else if (co.isExit()) {
				// 情况：太久没有使用操作，导致连接过期，自动关闭
				co._i状态 = MyConnection.I异常关闭;
			} else if (co.isTimeout()) {
				co._i状态 = MyConnection.I超时;
				co.exit();
			} else {
				co._i状态 = MyConnection.I使用中;
				return co;
			}
			if (i > 10) {// 连续异常 大约5s 那么需要检查整个环境 基本上是断网了
				Connection cn = fv尝试连接();
				try {
					cn.close();
				} catch (SQLException e) {
					return null;
				}
				fv检查所有连接();
				i = 0;
			}
		}
	}

	//////////////////////////////////// 扩展部分////////////////////////////////
	/// ----------------getconnection相关--------------
	/**
	 * 取出连接,如果连接已经关闭,就替换掉
	 */
	@Override
	protected MyConnection fv从连接池取出下一个连接() {
		if (i可用连接数.intValue() < 1)
			return null;
		MyConnection co = queue.poll();
		if (co == null) // 取不到数据
			return null;
		i可用连接数.decrementAndGet();
		return co;
	}

	@Override
	protected void fv发现连接异常关闭(MyConnection co) {
		co._i状态 = MyConnection.I异常关闭;
		co.reclock();
	}

	protected void fv插入有效队列(MyConnection co) {// ok
		co._i状态 = MyConnection.I空闲;
		i可用连接数.incrementAndGet();
		co.reclock();
		queue.offer(co);
	}

	/* 创建连接，增加累计连接数 */
	@Override
	protected void fv新增连接(MyConnection co) {// ok
		int i = i在册连接数.intValue();
		if (i >= I最大连接数量)
			return;
		i在册连接数.incrementAndGet();
		co.setId(i);
		connpool.add(co);
		fv插入有效队列(co);
	}

	/// ----------------getconnection相关--------------
	/**
	 * 关闭连接池中所有的连接，并清空连接池。
	 * 
	 * @throws SQLException
	 */
	@Override
	public synchronized void exitAll() throws SQLException {// ok
		// 确保连接池存在，如果不存在，返回
		int max = i在册连接数.intValue();
		if (max == 0) {
			return;
		}
		MyConnection pConn = null;
		for (int i = 0; i < max; i++) {
			pConn = connpool.get(i);
			if (pConn != null) {
				pConn.exit();
				pConn = null;
			}
		}
		// 置连接池为空
		connpool.clear();
		queue.clear();
		i在册连接数 = new AtomicInteger(0);
		i可用连接数 = new AtomicInteger(0);
	}

	@Override
	public String showInfo(String s) {// ok
		StringBuilder sb = new StringBuilder(s);
		sb.append("\t线:").append(Thread.currentThread().getId());
		sb.append("\t时:").append(MyFun.getMs());
		sb.append("\t累计:").append(i在册连接数.intValue());
		sb.append(",可用:").append(this.i可用连接数.intValue());
		sb.append(",最大:").append(this.I最大连接数量);
		int yc = 0, cs = 0;
		for (int i检查 = 0; i检查 < connpool.size(); i检查++) {
			MyConnection co = connpool.get(i检查);
			if (co.isExit()) {
				yc++;
			} else if (co.isTimeout()) {
				cs++;
			}
		}
		sb.append(",关闭:").append(yc);
		sb.append(",超时:").append(cs);
		String ss = sb.toString();
		Log.info(ss);
		return ss;
	}

	public void showConnInfo(MyConnection con, int id) {
		StringBuilder sb = new StringBuilder();
		sb.append("\t线:").append(Thread.currentThread().getId());
		sb.append("\t时:").append(MyFun.getMs());
		sb.append("，id:").append(id);
		sb.append(",getId:").append(con.getId());
		sb.append(",关闭:").append(con.isClosed());
		sb.append(",超时:").append(con.isTimeout());
		sb.append(",状态:").append(con._i状态);
		String ss = sb.toString();
		Log.info(ss);
	}
////////////////////////////////////扩展部分--end////////////////////////////////
}